//========= Copyright Valve Corporation ============//

#include "vrpathregistry_public.h"
#include "json/json.h"
#include "pathtools_public.h"
#include "envvartools_public.h"
#include "strtools_public.h"
#include "dirtools_public.h"

#if defined( WIN32 )
#include <windows.h>
#include <shlobj.h>

#undef GetEnvironmentVariable
#elif defined OSX
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
#elif defined(LINUX)
#include <dlfcn.h>
#include <stdio.h>
#endif

#include <algorithm>

#ifndef VRLog
#if defined( __MINGW32__ )
#define VRLog(args...)		fprintf(stderr, args)
#elif defined( WIN32 )
#define VRLog(fmt, ...)		fprintf(stderr, fmt, __VA_ARGS__)
#else
#define VRLog(args...)		fprintf(stderr, args)
#endif
#endif

/** Returns the root of the directory the system wants us to store user config data in */
static std::string GetAppSettingsPath()
{
#if defined( WIN32 )
	WCHAR rwchPath[MAX_PATH];

	if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, rwchPath)))
	{
		return "";
	}

	// Convert the path to UTF-8 and store in the output
	std::string sUserPath = UTF16to8(rwchPath);

	return sUserPath;
#elif defined( OSX )
	std::string sSettingsDir;
	@autoreleasepool {
		// Search for the path
		NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
		if ([paths count] == 0)
		{
			return "";
		}

		NSString* resolvedPath = [paths objectAtIndex : 0];
		resolvedPath = [resolvedPath stringByAppendingPathComponent : @"OpenVR"];

		if (![[NSFileManager defaultManager]createDirectoryAtPath:resolvedPath withIntermediateDirectories : YES attributes : nil error : nil] )
		{
			return "";
		}

		sSettingsDir.assign([resolvedPath UTF8String]);
	}
	return sSettingsDir;
#elif defined( LINUX )

	// As defined by XDG Base Directory Specification 
	// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html

	const char* pchHome = getenv("XDG_CONFIG_HOME");
	if ((pchHome != NULL) && (pchHome[0] != '\0'))
	{
		return pchHome;
	}

	//
	// XDG_CONFIG_HOME is not defined, use ~/.config instead
	// 
	pchHome = getenv("HOME");
	if (pchHome == NULL)
	{
		return "";
	}

	std::string sUserPath(pchHome);
	sUserPath = Path_Join(sUserPath, ".config");
	return sUserPath;
#else
	#warning "Unsupported platform"
#endif
}


// ---------------------------------------------------------------------------
// Purpose: Constructor
// ---------------------------------------------------------------------------
CVRPathRegistry_Public::CVRPathRegistry_Public()
{

}

// ---------------------------------------------------------------------------
// Purpose: Computes the registry filename
// ---------------------------------------------------------------------------
std::string CVRPathRegistry_Public::GetOpenVRConfigPath()
{
	std::string sConfigPath = GetAppSettingsPath();
	if (sConfigPath.empty())
		return "";

#if defined( _WIN32 ) || defined( LINUX )
	sConfigPath = Path_Join(sConfigPath, "openvr");
#elif defined ( OSX ) 
	sConfigPath = Path_Join(sConfigPath, ".openvr");
#else
	#warning "Unsupported platform"
#endif
		sConfigPath = Path_FixSlashes(sConfigPath);
	return sConfigPath;
}



//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
std::string CVRPathRegistry_Public::GetVRPathRegistryFilename()
{
	std::string sPath = GetOpenVRConfigPath();
	if (sPath.empty())
		return "";

#if defined( _WIN32 )
	sPath = Path_Join(sPath, "openvrpaths.vrpath");
#elif defined ( POSIX ) 
	sPath = Path_Join(sPath, "openvrpaths.vrpath");
#else
#error "Unsupported platform"
#endif
	sPath = Path_FixSlashes(sPath);
	return sPath;
}


// ---------------------------------------------------------------------------
// Purpose: Converts JSON to a history array
// ---------------------------------------------------------------------------
static void ParseStringListFromJson(std::vector< std::string >* pvecHistory, const Json::Value& root, const char* pchArrayName)
{
	if (!root.isMember(pchArrayName))
		return;

	const Json::Value& arrayNode = root[pchArrayName];
	if (!arrayNode)
	{
		VRLog("VR Path Registry node %s is not an array\n", pchArrayName);
		return;
	}

	pvecHistory->clear();
	pvecHistory->reserve(arrayNode.size());
	for (uint32_t unIndex = 0; unIndex < arrayNode.size(); unIndex++)
	{
		std::string sPath(arrayNode[unIndex].asString());
		pvecHistory->push_back(sPath);
	}
}


// ---------------------------------------------------------------------------
// Purpose: Converts a history array to JSON
// ---------------------------------------------------------------------------
static void StringListToJson(const std::vector< std::string >& vecHistory, Json::Value& root, const char* pchArrayName)
{
	Json::Value& arrayNode = root[pchArrayName];
	for (auto i = vecHistory.begin(); i != vecHistory.end(); i++)
	{
		arrayNode.append(*i);
	}
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CVRPathRegistry_Public::ToJsonString(std::string& sJsonString)
{
	std::string sRegPath = GetVRPathRegistryFilename();
	if (sRegPath.empty())
		return false;

	std::string sRegistryContents = Path_ReadTextFile(sRegPath);
	if (sRegistryContents.empty())
		return false;

	sJsonString = sRegistryContents;

	return true;
}


// ---------------------------------------------------------------------------
// Purpose: Loads the config file from its well known location
// ---------------------------------------------------------------------------
bool CVRPathRegistry_Public::BLoadFromFile()
{
	std::string sRegPath = GetVRPathRegistryFilename();
	if (sRegPath.empty())
	{
		VRLog("Unable to determine VR Path Registry filename\n");
		return false;
	}

	std::string sRegistryContents = Path_ReadTextFile(sRegPath);
	if (sRegistryContents.empty())
	{
		VRLog("Unable to read VR Path Registry from %s\n", sRegPath.c_str());
		return false;
	}

	Json::Value root;
	Json::Reader reader;

	if (!reader.parse(sRegistryContents, root))
	{
		VRLog("Unable to parse %s: %s\n", sRegPath.c_str(), reader.getFormattedErrorMessages().c_str());
		return false;
	}

	ParseStringListFromJson(&m_vecRuntimePath, root, "runtime");
	ParseStringListFromJson(&m_vecConfigPath, root, "config");
	ParseStringListFromJson(&m_vecLogPath, root, "log");
	if (root.isMember("external_drivers") && root["external_drivers"].isArray())
	{
		ParseStringListFromJson(&m_vecExternalDrivers, root, "external_drivers");
	}

	return true;
}


// ---------------------------------------------------------------------------
// Purpose: Saves the config file to its well known location
// ---------------------------------------------------------------------------
bool CVRPathRegistry_Public::BSaveToFile() const
{
#if defined( DASHBOARD_BUILD_MODE )
	return false;
#else
	std::string sRegPath = GetVRPathRegistryFilename();
	if (sRegPath.empty())
		return false;

	Json::Value root;

	root["version"] = 1;
	root["jsonid"] = "vrpathreg";

	StringListToJson(m_vecRuntimePath, root, "runtime");
	StringListToJson(m_vecConfigPath, root, "config");
	StringListToJson(m_vecLogPath, root, "log");
	StringListToJson(m_vecExternalDrivers, root, "external_drivers");

	Json::StyledWriter writer;
	std::string sRegistryContents = writer.write(root);

	// make sure the directory we're writing into actually exists
	std::string sRegDirectory = Path_StripFilename(sRegPath);
	if (!BCreateDirectoryRecursive(sRegDirectory.c_str()))
	{
		VRLog("Unable to create path registry directory %s\n", sRegDirectory.c_str());
		return false;
	}

	if (!Path_WriteStringToTextFile(sRegPath, sRegistryContents.c_str()))
	{
		VRLog("Unable to write VR path registry to %s\n", sRegPath.c_str());
		return false;
	}

	return true;
#endif
}


// ---------------------------------------------------------------------------
// Purpose: Returns the current runtime path or NULL if no path is configured.
// ---------------------------------------------------------------------------
std::string CVRPathRegistry_Public::GetRuntimePath() const
{
	if (m_vecRuntimePath.empty())
		return "";
	else
		return m_vecRuntimePath.front().c_str();
}


// ---------------------------------------------------------------------------
// Purpose: Returns the current config path or NULL if no path is configured.
// ---------------------------------------------------------------------------
std::string CVRPathRegistry_Public::GetConfigPath() const
{
	if (m_vecConfigPath.empty())
		return "";
	else
		return m_vecConfigPath.front().c_str();
}


// ---------------------------------------------------------------------------
// Purpose: Returns the current log path or NULL if no path is configured.
// ---------------------------------------------------------------------------
std::string CVRPathRegistry_Public::GetLogPath() const
{
	if (m_vecLogPath.empty())
		return "";
	else
		return m_vecLogPath.front().c_str();
}



// ---------------------------------------------------------------------------
// Purpose: Returns paths using the path registry and the provided override 
//			values. Pass NULL for any paths you don't care about.
// ---------------------------------------------------------------------------
bool CVRPathRegistry_Public::GetPaths(std::string* psRuntimePath, std::string* psConfigPath, std::string* psLogPath, const char* pchConfigPathOverride, const char* pchLogPathOverride, std::vector<std::string>* pvecExternalDrivers)
{
	CVRPathRegistry_Public pathReg;
	bool bLoadedRegistry = pathReg.BLoadFromFile();
	int nCountEnvironmentVariables = 0;

	if (psRuntimePath)
	{
		if (GetEnvironmentVariable(k_pchRuntimeOverrideVar).length() != 0)
		{
			*psRuntimePath = GetEnvironmentVariable(k_pchRuntimeOverrideVar);
			nCountEnvironmentVariables++;
		}
		else if (!pathReg.GetRuntimePath().empty())
		{
			*psRuntimePath = pathReg.GetRuntimePath();
		}
		else
		{
			*psRuntimePath = "";
		}
	}

	if (psConfigPath)
	{
		if (GetEnvironmentVariable(k_pchConfigOverrideVar).length() != 0)
		{
			*psConfigPath = GetEnvironmentVariable(k_pchConfigOverrideVar);
			nCountEnvironmentVariables++;
		}
		else if (pchConfigPathOverride)
		{
			*psConfigPath = pchConfigPathOverride;
		}
		else if (!pathReg.GetConfigPath().empty())
		{
			*psConfigPath = pathReg.GetConfigPath();
		}
		else
		{
			*psConfigPath = "";
		}
	}

	if (psLogPath)
	{
		if (GetEnvironmentVariable(k_pchLogOverrideVar).length() != 0)
		{
			*psLogPath = GetEnvironmentVariable(k_pchLogOverrideVar);
			nCountEnvironmentVariables++;
		}
		else if (pchLogPathOverride)
		{
			*psLogPath = pchLogPathOverride;
		}
		else if (!pathReg.GetLogPath().empty())
		{
			*psLogPath = pathReg.GetLogPath();
		}
		else
		{
			*psLogPath = "";
		}
	}

	if (pvecExternalDrivers)
	{
		*pvecExternalDrivers = pathReg.m_vecExternalDrivers;
	}

	if (nCountEnvironmentVariables == 3)
	{
		// all three environment variables were set, so we don't need the physical file
		return true;
	}

	return bLoadedRegistry;
}

